﻿
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MCPlatform.Communicate.TcpIp
{
    public class NTcpClient : IDisposable, INotifyPropertyChanged
    {
         NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        public string? ClientName { get; set; }    

        private Thread? _rxThread = null;

        private List<byte> _queuedMsg = new List<byte>();

        private TcpClient? _client = null;

        private bool disposedValue = false;

        public byte Delimiter { get; set; }

        public bool IsRetryConnect { get; set; } = false;

        public bool IsWriteBusy { get; set; }

        public bool Connected
        {
            get
            {
                if (_client == null || _client.Client?.Connected == false )
                {
                   
                    return false;
                }

                //if (_client.Client?.Poll(1, SelectMode.SelectRead) == true && _client.Available == 0)
                //{
                //    return false;
                //}
               
                return true;
            }
        }//=> _client?.Connected ?? false;

        private bool isConnected;

        public bool IsConnected
        {
            get { return isConnected; }
            set { isConnected = value; RaisePropertyChanged(); }
        }

        //重连次数
        public int RetryCount { get; set; } = 0;
        public Encoding StringEncoder { get; set; }

        internal bool QueueStop { get; set; }

        internal int ReadLoopIntervalMs { get; set; }

        public bool AutoTrimStrings { get; set; }

        public TcpClientModel TcpClientInfo { get; set; }

        Message? mReply = null;

        Message? mReply2 = null;
        IPAddress iP => IPAddress.Parse(TcpClientInfo.IPAddr);
        public TcpClient? TcpClient => _client;

        public event EventHandler<Message>? DelimiterDataReceived;

        public event EventHandler<Message> DataReceived;
        public event PropertyChangedEventHandler? PropertyChanged;

        protected void RaisePropertyChanged([CallerMemberName] string? name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        public void Set<T>(ref T field, T value, [CallerMemberName] string propName = "")
        {
            if (EqualityComparer<T>.Default.Equals(field, value))
            {
                return;
            }
            field = value;
            RaisePropertyChanged(propName);
        }
        public NTcpClient(TcpClientModel tcpClientInfo)
        {
            StringEncoder = Encoding.UTF8;
            ReadLoopIntervalMs = 10;
            Delimiter = 19;
            this.TcpClientInfo = tcpClientInfo;

            DataReceived += NTcpClient_DataReceived;
        }

        private void NTcpClient_DataReceived(object? sender, Message e)
        {
            mReply = e;
            mReply2 = e;
        }

        public bool Connect()
        {
            try
            {
                StartRxThread();
                _client = new TcpClient();
                var result = _client.BeginConnect(iP, TcpClientInfo.Port, null, null);
                var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
                if (!success)
                {
                    logger.Error($"NTcpClient {TcpClientInfo.ClientName}Connect fail,ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
                    throw new Exception("Fail to connect.");
                    
                }
                _client.EndConnect(result);
                logger.Info($"NTcpClient {TcpClientInfo.ClientName}Connect,ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
                
                return _client.Connected;

            }
            catch (Exception)
            {
                logger.Error($"NTcpClient {TcpClientInfo.ClientName}Connect fail,ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
                return false;
            }

        }
      

        private void StartRxThread()
        {
            if (_rxThread == null)
            {
                _rxThread = new Thread(new ParameterizedThreadStart(ListenerLoop));
                _rxThread.IsBackground = true;
                _rxThread.Start();
            }
        }

        public NTcpClient Disconnect()
        {
            if (_client == null)
            {
                return this;
            }
            logger.Info($"NTcpClient {TcpClientInfo.ClientName} Disconnect,ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
            _client.Close();
            _client = null;
            return this;
        }

        private void ListenerLoop(object? state)
        {
            while (!QueueStop)
            {
                try
                {
                    RunLoopStep();
                    IsRetryConnect = this.TcpClientInfo.IsAutoRetry;
                    IsConnected = this.Connected;
                    if (this.Connected)
                    {
                        RetryCount = 0;
                    }

                }
                catch (Exception ex)
                {
                   logger.Error($"{ex.Message}");
                }

                Thread.Sleep(ReadLoopIntervalMs);
            }

            _rxThread = null;
        }

        private void RunLoopStep()
        {
            if ((_client == null || !_client.Connected) && !IsRetryConnect)
            {
                return;
            }
            else if (IsRetryConnect && !this.Connected)//尝试重连
            {
                
                Thread.Sleep(2000);//延迟2s
                if (RetryCount >= 10) //重连10次
                {
                    return;
                }
                if (!Connect())
                {
                    RetryCount++;
                    return;
                }
               
                
               
            }
           
            byte delimiter = Delimiter;
            if (_client is null)
            {
                return;
            }
            TcpClient client = _client;
            if (client?.Available == 0)
            {
                Thread.Sleep(10);
                return;
            }

            List<byte> list = new List<byte>();
            while (client?.Available > 0 && client.Connected)
            {
                byte[] array = new byte[1];
                client.Client.Receive(array, 0, 1, SocketFlags.None);
                list.AddRange(array);
                if (array[0] == delimiter)
                {
                    byte[] msg = _queuedMsg.ToArray();
                    _queuedMsg.Clear();
                    NotifyDelimiterMessageRx(client, msg);
                }
                else
                {
                    _queuedMsg.AddRange(array);
                }
            }

            if (list.Count > 0)
            {
                if (client is null)
                {
                    return;
                }
                NotifyEndTransmissionRx(client, list.ToArray());
            }
        }

        private void NotifyDelimiterMessageRx(TcpClient client, byte[] msg)
        {
            if (this.DelimiterDataReceived != null)
            {
                Message e = new Message(msg, client, StringEncoder, Delimiter, AutoTrimStrings);
                this.DelimiterDataReceived(this, e);
                logger.Info($"NTcpClient {TcpClientInfo.ClientName} DelimiterDataReceived:{e.MessageString},ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
            }
        }

        private void NotifyEndTransmissionRx(TcpClient client, byte[] msg)
        {
            if (this.DataReceived != null)
            {
                Message e = new Message(msg, client, StringEncoder, Delimiter, AutoTrimStrings);
                this.DataReceived(this, e);
                logger.Info($"NTcpClient {TcpClientInfo.ClientName} DataReceived:{e.MessageString},ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
            }
        }

        public void Write(byte[] data)
        {
            if (_client == null || !_client.Connected || !this.Connected)
            {
                
               // throw new Exception("Cannot send data to a null TcpClient (check to see if Connect was called)");
                return;
            }
            try
            {
                if (!_client.Connected)
                {
                   // throw new Exception("Connected fail!!");
                    return;
                }
                _client.GetStream().Write(data, 0, data.Length);
            }
            catch (Exception)
            {
                return;
                throw;
            }
           
        }

        public void Write(string data)
        {
            if (data != null)
            {
                Write(StringEncoder.GetBytes(data));
                logger.Info($"NTcpClient {TcpClientInfo.ClientName} Write:{data},ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
            }
        }

        public void WriteLine(string data)
        {
            if (!string.IsNullOrEmpty(data))
            {
                if ((uint)data.LastOrDefault() != Delimiter)
                {
                    Write(data + StringEncoder.GetString(new byte[1] { Delimiter }));
                }
                else
                {
                    Write(data);
                }
            }
        }

        public async Task<Message?> WriteAndGetReply(string data, TimeSpan timeout)
        {
            if (IsWriteBusy)
            {
                Stopwatch stopwatch1 = new Stopwatch();
                stopwatch1.Start();
                await Task.Run(() =>
                {
                    while (IsWriteBusy && stopwatch1.Elapsed < timeout)
                    {
                        Thread.Sleep(10);
                    }

                });
            }
            IsWriteBusy = true; 
            mReply = null;
            //DataReceived += delegate (object? s, Message e)
            //{
            //    mReply = e;
            //};
            Write(data);
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            await Task.Run(() =>
            {
                while (mReply == null && stopwatch.Elapsed < timeout)
                {
                    Thread.Sleep(10);
                }
            });

            IsWriteBusy = false;
            return mReply;
        }
        public Message? WriteLineAndGetReply(string data, TimeSpan timeout)
        {
            mReply2 = null;
           
            WriteLine(data);
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            while (mReply2 == null && stopwatch.Elapsed < timeout)
            {
                Thread.Sleep(10);
            } 

            return mReply2;
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposedValue)
            {
                return;
            }

            if (disposing)
            {
            }

            QueueStop = true;
            if (_client != null)
            {
                try
                {
                    _client.Close();
                }
                catch
                {
                }

                _client = null;
            }

            disposedValue = true;
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            logger.Info($"NTcpClient {TcpClientInfo.ClientName}Dispose,ip:{TcpClientInfo.IPAddr},port:{TcpClientInfo.Port}");
        }
    }
}


